#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
CellStore save data as file for Cell. 
'''

__author__ = 'kuerant < kuerant@gmail.com >'
__version__ = '1.0.0'


import os, os.path
import time

import error
import log

from const import COLUMN_NOT_FOUND,COLUMN_FOUND,COLUMN_DELETED

#######################################################################

class   CellStore(object) :
    '''CellStore save data of cell into disk file.
    '''
    MODE_READONLY = 0x7901
    MODE_APPEND   = 0x7902

    APPEND_FILENAME = 'store'

    SEPARATOR_KEY_VALUE = '\t'
    TAG_MARK_BEGIN = 'CellTagBegin'
    TAG_MARK_END   = 'CellTagEnd'
    SIZE_OF_BLOCK_TO_INDEX = 65536

    # TAG_STRING ::= BEGIN_MARK RECORD_DATA_END RECORD_INDEX_END BLOCK_INDEX_END END_MARK
    TAG_STRING_LENGTH = 12 + 1 + 16 + 1 + 16 + 1 + 16 + 1 + 10   # no trailling \n

    def __init__(self, dir) :
        '''
        @param dir where storage files locate
        '''
        self._dir = dir
        self._mode = CellStore.MODE_APPEND

        self._iter_filehandle = None

        self._append_filename = None
        self._append_filehandle = None

        self.write_record_indexes = []
        self.write_block_indexes = []
        self.write_block_indexes_size = 0   # accumulate size-of-record-index to mark block index
        self.write_block_indexes_offset = 0
        self.write_block_indexes_last_key = None

        self.read_record_data_end_offset  = 0
        self.read_record_index_end_offset = 0
        self.read_block_index_end_offset  = 0
        self.read_record_indexes = {}
        self.read_block_indexes = []        # bisect

        ## __init__()

    def __del__(self) :
        if self._iter_filehandle :
            self._iter_filehandle.close()
            self._iter_filehandle = None

        if self._append_filehandle :
            self.freeze()
        ## __del__()

    ###################################################################

    def __iter__(self) :
        # do some initialization for iteration
        if not self._iter_filehandle :
            self._iter_filename = self.__getIterableStoreFileName()
            log.debug('Iterating CellStore %s', self._iter_filename)
            if self._iter_filename is None : 
                # no iterable store filename
                return self

            filepath = os.sep.join((self._dir, self._iter_filename))
            if os.path.exists(filepath) :
                handle = open(filepath, 'r')
                self._iter_filename = filepath
                self._iter_filehandle = handle

                self.__init_read()
                self._mode = CellStore.MODE_READONLY
            else :
                self._mode = CellStore.MODE_APPEND
                raise IOError,'"%s" does NOT exist.' % filepath
        return self
        ## __iter__()

    def next(self) :
        '''
        @return (key,data)
        '''
        if CellStore.MODE_APPEND == self._mode : 
            # wrong mode
            self._iter_filehandle = None
            raise StopIteration
        if not self._iter_filehandle : 
            # not initialized
            self._iter_filehandle = None
            raise StopIteration

        offset = self._iter_filehandle.tell()
        if offset >= self.read_record_data_end_offset :
            self._iter_filehandle.close()
            self._iter_filehandle = None
            raise StopIteration

        line = self._iter_filehandle.readline().strip()
        if not line :
            self._iter_filehandle.close()
            self._iter_filehandle = None
            raise StopIteration
        key, value = line.split(self.SEPARATOR_KEY_VALUE, 1)
        return key,value

        ## next()
        
    def __getIterableStoreFileName(self) :
        last_timestamp = 0
        last_filename = None
        items = os.listdir(self._dir)
        for item in items :
            prefix,suffix = item.rsplit('.', 1)
            if not suffix : continue
            timestamp = int(suffix)
            if timestamp > last_timestamp :
                last_timestamp = timestamp
                last_filename = item

        return last_filename
        ## __getIterableStoreFileName()

    def __init_read(self) :
        SEEK_END = 2
        self._iter_filehandle.seek(-self.TAG_STRING_LENGTH, SEEK_END)
        read_buffer_tag = self._iter_filehandle.read(self.TAG_STRING_LENGTH)
        #print self.read_buffer_tag
        tag_begin, record_data_end_offset, record_index_end_offset, block_index_end_offset, tag_end = read_buffer_tag.split(self.SEPARATOR_KEY_VALUE)
        self.read_record_data_end_offset  = int(record_data_end_offset)
        self.read_record_index_end_offset = int(record_index_end_offset)
        self.read_block_index_end_offset  = int(block_index_end_offset)

        ## read block Index
        self._iter_filehandle.seek(self.read_record_index_end_offset)
        offset = self._iter_filehandle.tell()
        while offset < self.read_block_index_end_offset :
            line = self._iter_filehandle.readline().strip()
            _key, _offset, _length = line.split('\t')
            self.read_block_indexes.append( (_key, int(_offset), int(_length)) )
            offset = self._iter_filehandle.tell()

        self._iter_filehandle.seek(0)
        # __init_read()

    ###################################################################

    def append(self, key, data) :
        if not self._append_filehandle :
            self.__init_append()

        offset = self._append_filehandle.tell()
        record = '%s%s%s\n' % (key, self.SEPARATOR_KEY_VALUE, data)
        self._append_filehandle.write(record)

        index = '%s\t%d\t%d\n' % (key, offset, len(record))
        self.write_record_indexes.append(index)

        self.write_block_indexes_last_key = key
        self.write_block_indexes_size += len(index)
        if self.write_block_indexes_size >= self.SIZE_OF_BLOCK_TO_INDEX :
            block_index = (key, self.write_block_indexes_offset, self.write_block_indexes_size)
            self.write_block_indexes.append(block_index)
            self.write_block_indexes_offset += self.write_block_indexes_size
            self.write_block_indexes_size = 0
            self.write_block_indexes_last_key = None

        ## append()

    def __writeIndex(self) :
        # this method should be call once and ONLY ONCE
        if not self._append_filehandle : return False

        record_data_end_offset = self._append_filehandle.tell()
        self._append_filehandle.writelines(self.write_record_indexes)

        record_index_end_offset = self._append_filehandle.tell()
        if self.write_block_indexes_last_key is not None :
            block_index = (self.write_block_indexes_last_key, self.write_block_indexes_offset, self.write_block_indexes_size)
            self.write_block_indexes.append(block_index)
            self.write_block_indexes_offset += self.write_block_indexes_size
            self.write_block_indexes_size = 0
            self.write_block_indexes_last_key = None
        #print self.write_block_indexes
        #print 'OFFSET to add:',record_data_end_offset
        block_indexes = [ '%s\t%d\t%d\n' % (x[0], x[1] + record_data_end_offset, x[2]) for x in self.write_block_indexes ]
        #print block_indexes
        self._append_filehandle.writelines(block_indexes)

        block_index_end_offset = self._append_filehandle.tell()

        tag_fields = (self.TAG_MARK_BEGIN, '%016d' % record_data_end_offset, '%016d' % record_index_end_offset, '%016d' % block_index_end_offset, self.TAG_MARK_END)
        write_buffer_tag = self.SEPARATOR_KEY_VALUE.join(tag_fields)
        self._append_filehandle.write(write_buffer_tag)
        return True
        # __writeIndex()

    def freeze(self) :
        '''freeze(close) to stop appending data to the CellStore,
        and the CellStore is READ-ONLY now.
        '''
        if self._append_filehandle :
            self.__writeIndex()
            self._append_filehandle.close()
            self._append_filehandle = None

            if self._append_filename :
                seconds_since_epoch = int(time.time())
                freeze_filename = '%s.%d' % (self._append_filename, seconds_since_epoch)
                os.rename(self._append_filename, freeze_filename)
                log.debug('CellStore %s froze.', freeze_filename)
        ## freeze()

    def __init_append(self) :
        if self._append_filehandle : return
        filename = os.sep.join((self._dir, CellStore.APPEND_FILENAME))
        filehandle = open(filename, 'a+')
        self._append_filehandle = filehandle
        self._append_filename = filename
        ## __init_append()

    ###################################################################

    def findColumn(self, row_key, column_key) :
        '''
        @return (status,data,timestamp)
        '''
        return (COLUMN_NOT_FOUND, None, None)
        ## findColumn()

    def getColumnStatus(self, row_key, column_key) :
        '''
        @return (status,timestamp)
        '''
        return (COLUMN_NOT_FOUND, None)
        ## getColumnStatus()

    ## class CellStore

